/* TODO
 * 
 * - submenus for items in menu ?
 * 
 * - post callbacks for apps menuitems ? free some stuff etc..
 * 
 * - more config options: 'move pointer to window', 'item width/item height' 'always group'
 * 
 * - group items, so that an item gets swapped to the menu of another item of the same class (half done)
 * 
 * - store the number of items to not have to recount them everytime
 * 
 * - init event handler only if a gcc is shown (this would apply to all modules, imho)
 *
 * - make vertical bar work (initial work done)
 * 
 * - sort function by desktops (for menu)
 * 
 * - check if swap is needed on resize (doesn't work, see BUGS)
 * 
 * -- would be good to have a more specific resize/orientation_callback to know on 
 *    changes of gcc if items have to be removed from the bar
 *
 */

/* BUGS
 * 
 * - don't change layout and orientation in shelf config at a time, the module is most likely to segv then
 * 
 * - update on border property changes doesnt work correctly: on skipwinlist changes the item has to be added/removed 
 * 
 * */

#include "e.h"
#include "e_mod_main.h"
#include "itask_item.h"
#include "itask_items.h"
#include "itask_menu.h"



static Itask *_itask_new(Evas *evas, E_Zone *zone);
static void	_itask_free(Itask *it);
static int  _itask_fill(Itask *it);
static int  _itask_empty(Itask *it);
static void _itask_orient_set(Itask *it, int horizontal);
static int  _itask_cb_event_desk_show(void *data, int type, void *event);

static E_Config_DD *conf_edd = NULL;
static E_Config_DD *conf_item_edd = NULL;

static int _itask_init_timer(void *data);

Config *itask_config = NULL;
char *itask_theme_path;


/* gadcon requirements */
static E_Gadcon_Client *_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style);
static void _gc_shutdown(E_Gadcon_Client *gcc);
static void _gc_orient(E_Gadcon_Client *gcc);
static char *_gc_label(void);
static Evas_Object *_gc_icon(Evas *evas);



static const E_Gadcon_Client_Class _gadcon_class =
   {
      GADCON_CLIENT_CLASS_VERSION,
      "itask",
      {_gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon},
      E_GADCON_CLIENT_STYLE_PLAIN
   };



static E_Gadcon_Client *
_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
{
   Itask *it;
   Evas_Object *o;
   E_Gadcon_Client *gcc;
   Instance *inst;
   Config_Item *ci;

   inst = E_NEW(Instance, 1);

   ci = itask_config_item_get(id);
   // TODO prefix all options with 'option_', better add substruct
   it = _itask_new(gc->evas, gc->zone);
   it->show_label = ci->show_label;
   it->show_zone = ci->show_zone;
   it->show_desk = ci->show_desk;
   it->icon_label = ci->icon_label;
   it->skip_always_below_windows = ci->skip_always_below_windows;
   it->skip_dialogs = ci->skip_dialogs;
   it->swap_on_focus = ci->swap_on_focus;
   it->option_iconify_focused = ci->iconify_focused;
   it->option_ibox_style = ci->ibox_style;
   it->inst = inst;
   inst->itask = it;
   
   o = it->o_box;
   gcc = e_gadcon_client_new(gc, name, id, style, o);
   gcc->data = inst;

   inst->gcc = gcc;
   inst->o_itask = o;

   itask_config->instances = evas_list_append(itask_config->instances, inst);

   it->item_height = 32; // TODO make this selectable between mix max size that the theme provides
   it->item_width = 114;

   itask_menu_button(it);
   itask_resize_handle(it);	
   
   _itask_fill(it);
   //it->init_timer = ecore_timer_add(0.5, _itask_init_timer, it);
   return gcc;
}



static void
_gc_shutdown(E_Gadcon_Client *gcc)
{  
   Instance *inst;

   inst = gcc->data;
   itask_config->instances = evas_list_remove(itask_config->instances, inst);
   _itask_free(inst->itask);
   free(inst);
}



static void
_gc_orient(E_Gadcon_Client *gcc)
{
   Instance *inst;
   Evas_Coord w, h;
   int button_size;    
   Itask_Item *ic;  
    
   inst = gcc->data;
   Itask *it = inst->itask;
   
   evas_object_geometry_get(it->inst->gcc->gadcon->o_container,
                NULL, NULL,&it->itask_width, &it->itask_height);
   
   switch (gcc->gadcon->orient)
   {
   case E_GADCON_ORIENT_FLOAT:
   case E_GADCON_ORIENT_HORIZ:
   case E_GADCON_ORIENT_TOP:
   case E_GADCON_ORIENT_BOTTOM:
   case E_GADCON_ORIENT_CORNER_TL:
   case E_GADCON_ORIENT_CORNER_TR:
   case E_GADCON_ORIENT_CORNER_BL:
   case E_GADCON_ORIENT_CORNER_BR:
      //if (!e_box_orientation_get(it->o_box))
        _itask_orient_set(inst->itask, 1); 
      
      int width;
      if(it->option_ibox_style)
      	 width = it->itask_height;
      else
         width = it->item_width;
      
      if(it->items_bar)
        w = (evas_list_count(it->items_bar) * width) + it->itask_height;
      else
        w = it->itask_height;
      
      h = it->itask_height;
      
      printf("_gc_orient: %dx%d\n",w,h);
      e_gadcon_client_aspect_set(gcc, w, h); 
      break;
      
   case E_GADCON_ORIENT_VERT:
   case E_GADCON_ORIENT_LEFT:
   case E_GADCON_ORIENT_RIGHT:
   case E_GADCON_ORIENT_CORNER_LT:
   case E_GADCON_ORIENT_CORNER_RT:
   case E_GADCON_ORIENT_CORNER_LB:
   case E_GADCON_ORIENT_CORNER_RB:
      //if (e_box_orientation_get(it->o_box))
        _itask_orient_set(inst->itask, 0);
      
      if(it->items_bar)
      {
      	if(it->option_ibox_style)
      	   h = (evas_list_count(it->items_bar) * it->itask_width) + it->itask_width;
        else   
           h = (evas_list_count(it->items_bar) * it->item_height) + it->item_height;
      }
      else
        h = it->item_height;
        
      if(it->option_ibox_style)
         w = it->itask_width;
      else
         w = it->item_width;
     //printf("_gc_orient: %dx%d\n",w,h);
      e_gadcon_client_aspect_set(gcc, w, h); 
      
      break;
      
   default:
      break;
   }
   if (evas_list_count(inst->itask->items) < 1)
      e_gadcon_client_aspect_set(gcc, 16, 16);
}



static char *
_gc_label(void)
{
   return _("Itask");
}



static Evas_Object *
_gc_icon(Evas *evas)
{
   Evas_Object *o;
   char buf[4096];

   o = edje_object_add(evas);
   snprintf(buf, sizeof(buf), "%s/module.edj",
            e_module_dir_get(itask_config->module));
   edje_object_file_set(o, buf, "icon");
   return o;
}


/***************************************************************************/




static Itask *
_itask_new(Evas *evas, E_Zone *zone)
{
   Itask *it;

   it = E_NEW(Itask, 1);
   it->o_box = e_box_add(evas);
   e_box_homogenous_set(it->o_box, 0);
   e_box_orientation_set(it->o_box, 1);
   e_box_align_set(it->o_box, 0.5, 0.5);
   it->zone = zone;
   it->items = NULL;
   it->items_menu = NULL;
   it->items_bar = NULL;
   return it;
}



static void
_itask_free(Itask *it)
{
   _itask_empty(it);
   evas_object_del(it->o_box);
   if (it->o_button)
      evas_object_del(it->o_button);
   free(it);
}



/* if somebody knows a better way, pleeease let me know */
static int _itask_init_timer(void *data)
{
   Itask *it;
   Itask_Item *ic;
   it = data;
   printf("init_timer\n");
   if(it->items_bar && !itask_item_space_left(it, 0))
   { 
   	printf("init_timer_swap\n");
   		itask_item_swap_oldest_from_bar(it);
   		itask_update_gc_orient(it);
   		return 1;
   }
   // a better alternative, doesn't work either
   /*if(it->init_items && it->init_items->data)
   {
   	    ic = it->init_items->data;
   		itask_item_realize(ic);
   		it->init_items = it->init_items->next;
   		return 1;
   }*/
   //ecore_timer_del(it->init_timer);
   //it->init_timer = NULL;
   return 0;
}


static int
_itask_fill(Itask *it)
{
   E_Border *bd;
   E_Border_List *bl;
   printf("_itask_fill\n");
   bl = e_container_border_list_first(it->zone->container);
   Itask_Item *ic;
   while(bd = e_container_border_list_next(bl))
   {
     ic = itask_item_new(it, bd);
     if(ic) itask_item_realize(ic);
   }
   e_container_border_list_free(bl);
  // this should move only as many items in the bar as space is available 
  //  disabled as it segv. see BUGS 
  // it->init_items = it->items;  
  // it->init_timer = ecore_timer_add(0.05, _itask_init_timer, it);
   return 0;
}


static int
_itask_empty(Itask *it)
{  	
   int changed = 0;
   while (it->items)
   {
      itask_item_remove(it->items->data);
      changed = 1;
   }
   return changed;
}



static void
_itask_orient_set(Itask *it, int horizontal)
{
   e_box_orientation_set(it->o_box, horizontal);
   e_box_align_set(it->o_box, 0.5, 0.5);
   itask_resize_handle(it);
}



EAPI void
itask_update_gc_orient(Itask *it)
{
  _gc_orient(it->inst->gcc);
}

EAPI void
itask_resize_handle(Itask *it)
{
   Evas_List *l;
   Itask_Item *ic;
   Evas_Coord w, h, bw;

   evas_object_geometry_get(it->inst->gcc->gadcon->o_container,
                                      NULL, NULL,&w, &h);
   
   if (e_box_orientation_get(it->o_box))
   	  bw = h;
   else
   {
   	  bw = it->item_width;
   	  h = it->item_height;
   }
   
   e_box_freeze(it->o_box);
   if(it->option_ibox_style)
   {
     e_box_pack_options_set(it->o_button,
                             1, 1, /* fill */
                             1, 1, /* expand */
                             0.5, 0.5, /* align */
                             0, 0, /* min */
                             -1, -1 /* max */
                            );

   }
   else
   {
     e_box_pack_options_set(it->o_button,
                             0, 0, /* fill */
                             0, 0, /* expand */
                             0.5, 0.5, /* align */
                             bw, h, /* min */
                             bw, h /* max */
                            );
   }
   e_box_thaw(it->o_box);
}



EAPI Evas_List *
itask_zone_find(E_Zone *zone)
{
   Evas_List *itask = NULL;
   Evas_List *l;

   for (l = itask_config->instances; l; l = l->next)
   {
      Instance *inst;
      Config_Item *ci;

      inst = l->data;
      ci = itask_config_item_get(inst->gcc->id);
      if (!ci)
         continue;

      if ((ci->show_zone == 0)
            || ((ci->show_zone == 1) && (inst->itask->zone == zone)))
         itask = evas_list_append(itask, inst->itask);

   }
   return itask;
}


// TODO hm, this can probably be made simpler
static int
_itask_cb_event_desk_show(void *data, int type, void *event)
{
   E_Event_Desk_Show *ev;
   Evas_List *l, *ll, *lll, *itask, *l_items = NULL;
   Itask *it;
   Itask_Item *ic, *ic2;
   
   ev = event;
   int items_on_desk_in_menu = 0;
   int items = 0;
   int items_on_desk_in_bar = 0;
   itask = itask_zone_find(ev->desk->zone);
   
   for (l = itask; l; l = l->next)
   {
      it = l->data;
      if(it->show_desk)
      {
      	for(ll = it->items_menu; ll ; ll = ll->next)
      	{
      		ic = ll->data;
      		if(ic->border->desk == ev->desk)
      		{
      			items_on_desk_in_menu++;
      			l_items = evas_list_append(l_items, ic);
      		}
      	}
      	if(items_on_desk_in_menu == 0) continue;

 		for(ll = it->items_bar; ll ; ll = ll->next)
      	{
      		ic = ll->data;
      		items++;
      		if(ic->border->desk == ev->desk)
      		{
      			items_on_desk_in_bar++;
      			ic->last_time = ecore_time_get();
      		}
      	}
      	if(items == items_on_desk_in_bar)
      	{
      	   while (l_items)
      	   {
      	 	  ic = l_items->data;
      	 	  ic->last_time = ecore_time_get();
      	 	  l_items = evas_list_remove_list(l_items, l_items);
      	   }
      	   continue;
      	}
      	 
      	int i;
        for(i = 0; items - items_on_desk_in_bar >= i  && i < items_on_desk_in_menu ; i++)
        {   
        	ic = l_items->data;
        	for(lll = l_items->next; lll ; lll = lll->next)
        	{
               ic2 = lll->data;
               if(ic->last_time < ic2->last_time)
               {
                  ic = ic2;
               }    		
        	}
        	
        	itask_item_swap_to_bar(ic);
        	itask_item_swap_oldest_from_bar(it);
        	l_items = evas_list_remove(l_items, ic);
        }
        while (l_items)
        {
      	    ic = l_items->data;
      	 	ic->last_time = ecore_time_get();
      		l_items = evas_list_remove_list(l_items, l_items);
        }
        itask_resize_handle(it);
      }
   }
   while (itask)
      itask = evas_list_remove_list(itask, itask);
  
   return 1;
}



EAPI Config_Item *
itask_config_item_get(const char *id)
{
   Evas_List *l;
   Config_Item *ci;

   for (l = itask_config->items; l; l = l->next)
   {
      ci = l->data;
      if ((ci->id) && (!strcmp(ci->id, id)))
         return ci;
   }
   ci = E_NEW(Config_Item, 1);
   ci->id = evas_stringshare_add(id);
   ci->show_label = 0;
   ci->show_zone = 1;
   ci->show_desk = 1;
   ci->icon_label = 0;
   ci->skip_always_below_windows = 0;
   ci->skip_dialogs = 0;
   ci->swap_on_focus = 1;
   ci->iconify_focused = 1;
   ci->ibox_style = 0;
   itask_config->items = evas_list_append(itask_config->items, ci);
   return ci;
}



EAPI void
itask_config_update(void)
{
   Evas_List *l;
   for (l = itask_config->instances; l; l = l->next)
   {
      Instance *inst;
      Itask *it;
      Config_Item *ci;

      inst = l->data;
      it = inst->itask;
      ci = itask_config_item_get(inst->gcc->id);
      it->show_label = ci->show_label;
      it->show_zone = ci->show_zone;
      it->show_desk = ci->show_desk;
      it->icon_label = ci->icon_label;
      it->skip_always_below_windows = ci->skip_always_below_windows;
      it->skip_dialogs = ci->skip_dialogs;
      it->swap_on_focus = ci->swap_on_focus;
      it->option_iconify_focused = ci->iconify_focused;
      it->option_ibox_style = ci->ibox_style;
      _itask_empty(it);
      _itask_fill(it);
   }
}



/***************************************************************************/


EAPI E_Module_Api e_modapi = { E_MODULE_API_VERSION, "Itask" };



EAPI void *
e_modapi_init(E_Module *m)
{
   conf_item_edd = E_CONFIG_DD_NEW("Itask_Config_Item", Config_Item);
   #undef T
   #undef D
   #define T Config_Item
   #define D conf_item_edd
   E_CONFIG_VAL(D, T, id, STR);
   E_CONFIG_VAL(D, T, show_label, INT);
   E_CONFIG_VAL(D, T, show_zone, INT);
   E_CONFIG_VAL(D, T, show_desk, INT);
   E_CONFIG_VAL(D, T, icon_label, INT);
   E_CONFIG_VAL(D, T, skip_always_below_windows, INT);
   E_CONFIG_VAL(D, T, skip_dialogs, INT);
   E_CONFIG_VAL(D, T, swap_on_focus, INT);
   E_CONFIG_VAL(D, T, iconify_focused, INT);
   E_CONFIG_VAL(D, T, ibox_style, INT);
   conf_edd = E_CONFIG_DD_NEW("Itask_Config", Config);
   #undef T
   #undef D
   #define T Config
   #define D conf_edd
   E_CONFIG_LIST(D, T, items, conf_item_edd);

   itask_config = e_config_domain_load("module.itask", conf_edd);
   if (!itask_config)
   {
      Config_Item *ci;

      itask_config = E_NEW(Config, 1);

      ci = E_NEW(Config_Item, 1);
      ci->id = evas_stringshare_add("0");
      ci->show_label = 0;
      ci->show_zone = 1;
      ci->show_desk = 1;
      ci->icon_label = 0;
      ci->skip_always_below_windows = 0;
      ci->skip_dialogs = 0;
      ci->swap_on_focus = 1;
      ci->iconify_focused = 1;
      ci->ibox_style = 0;
      itask_config->items = evas_list_append(itask_config->items, ci);
   }

   itask_config->module = m;

   itask_config->handlers = evas_list_append
                            (itask_config->handlers, ecore_event_handler_add
                             (E_EVENT_DESK_SHOW, _itask_cb_event_desk_show, NULL));

   e_gadcon_provider_register(&_gadcon_class);

   char buf[4096];
   snprintf(buf, sizeof(buf), "%s/itask.edj", e_module_dir_get(itask_config->module));
   itask_theme_path = strdup(buf);

   itask_items_init(itask_config);
   return m;
}



EAPI int
e_modapi_shutdown(E_Module *m)
{

   e_gadcon_provider_unregister(&_gadcon_class);

   while (itask_config->handlers)
   {
      ecore_event_handler_del(itask_config->handlers->data);
      itask_config->handlers = evas_list_remove_list(itask_config->handlers, itask_config->handlers);
   }

   while (itask_config->config_dialog)
      e_object_del(E_OBJECT(itask_config->config_dialog->data));

   while (itask_config->items)
   {
      Config_Item *ci;

      ci = itask_config->items->data;
      itask_config->items = evas_list_remove_list(itask_config->items, itask_config->items);
      if (ci->id)
         evas_stringshare_del(ci->id);
      free(ci);
   }

   free(itask_config);
   itask_config = NULL;

   free(itask_theme_path);
   itask_theme_path = NULL;

   E_CONFIG_DD_FREE(conf_item_edd);
   E_CONFIG_DD_FREE(conf_edd);

   return 1;
}



EAPI int
e_modapi_save(E_Module *m)
{
   e_config_domain_save("module.itask", conf_edd, itask_config);
   return 1;
}



EAPI int
e_modapi_about(E_Module *m)
{
   e_module_dialog_show(m,_("Enlightenment Itask Module"),
                        _("This is the Itask Application module for Enlightenment.<br>"
                          "It will hold all of your open applications for fast switching<br>"
                          "<br>"
                          "it has three basic functionalities that you dont wanna miss:<br>"
                          "1. If you click on an icon the window will be shown, yeah;<br>"
                          "  and if its on another desk, then you will be switched right to it. <br>"
                          "2. If you drag an icon out of Itask the Window will be shown on the<br>"
                          "  current desktop.<br>"
                          "3. Drag an icon to a pagers desk to send it there.<br>"
                          " Ok this info isn't so up to date... play wid it <br>"));
   return 1;
}
